package com.study.chunguard.common.utils.easyexcel;

import cn.hutool.core.collection.CollUtil;
import com.alibaba.excel.EasyExcelFactory;
import com.alibaba.excel.ExcelWriter;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.enums.CellDataTypeEnum;
import com.alibaba.excel.event.AnalysisEventListener;
import com.alibaba.excel.metadata.Head;
import com.alibaba.excel.metadata.data.WriteCellData;
import com.alibaba.excel.util.MapUtils;
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
import com.alibaba.excel.write.handler.SheetWriteHandler;
import com.alibaba.excel.write.metadata.WriteSheet;
import com.alibaba.excel.write.metadata.fill.FillConfig;
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
import com.alibaba.excel.write.style.column.AbstractColumnWidthStyleStrategy;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.study.chunguard.common.exception.BusinessException;
import com.study.chunguard.common.exception.code.ResponseCode;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.poi.ss.usermodel.Cell;
import org.apache.poi.ss.util.CellRangeAddress;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.function.BiFunction;

/**
 * Excel帮助类，支持从Excel文件中消费数据、数据保存到Excel Servlet响应中、数据保存到Excel本地文件
 * <p>
 * 重点介绍，定制化表头和合并单元格的使用方法，demo 如下：
 *
 * <blockquote><pre>
 * // 创建数据行列表
 * ArrayList<DemoVo> rows = generateSampleData();
 * // 使用EasyExcel写入数据
 * ArrayList<CellRangeAddress> cellRangeAddressList = new ArrayList<>();
 * // 合并单元格 也就是合并第一行的第1列到第8列
 * cellRangeAddressList.add(new CellRangeAddress(0, 0, 0, 7));
 * // 表头是一个二维数组，每个元素是一个List<String>，表示一列表头
 * ArrayList<List<String>> headers = new ArrayList<>();
 * String dynamicValue = new SimpleDateFormat().format(new Date());
 * // 这一列的第 2 和 3 行值相同，默认合并
 * headers.add(new ArrayList<>(Arrays.asList(dynamicValue, "默认合并", "默认合并")));
 * headers.add(new ArrayList<>(Arrays.asList("1", "11", "事件信息1")));
 * headers.add(new ArrayList<>(Arrays.asList("2", "22", "事件信息2")));
 * headers.add(new ArrayList<>(Arrays.asList("3", "33", "事件信息3")));
 * headers.add(new ArrayList<>(Arrays.asList("4", "44", "事件信息4")));
 * headers.add(new ArrayList<>(Arrays.asList("5", "55", "事件信息5")));
 * headers.add(new ArrayList<>(Arrays.asList("6", "66", "事件信息6")));
 * headers.add(new ArrayList<>(Arrays.asList("7", "77", "事件信息7")));
 * EasyExcelHelper.fillExcelRows("test.xlsx", DemoVo.class, rows, cellRangeAddressList, CustomHeadersHolder.builder().headers(headers).build());
 * </pre></blockquote>
 * <p>
 * demo 的效果类似于：
 *
 * <blockquote><pre>
 * |                                24-7-19 下午3:34                             |
 * |默认合并(上半部分)|11      |22      |33     |44      |55      |66     |77      |
 * |默认合并(下半部分)|事件信息1|事件信息2|事件信息3|事件信息4|事件信息5|事件信息6|事件信息7|
 * </pre></blockquote>
 * <p>
 * 测试体验见：{@link com.study.chunguard.common.utils.easyexcel.EasyExcelHelperTest}
 *
 * @author zhaowenhao
 * @since 2021-12-06
 */
@Slf4j
public class EasyExcelHelper {

    /**
     * 通用错误消息
     */
    public static final String COMMON_ERROR_MSG = "写入Excel文件失败";

    /**
     * 默认表格大小
     */
    public static final int DEFAULT_SHEET_SIZE = 100000;

    /**
     * 从MultipartFile对象代表的Excel文件中消费行数据
     *
     * @param <T>           行对象泛型类
     * @param multipartFile MultipartFile对象
     * @param rowClass      行对象类表示
     * @param rowsConsumer  记录行消费者
     * @param headRowNumber 表头大小控制，从第几行开始读数据
     */
    public static <T> void consumeExcelRows(MultipartFile multipartFile, Class<T> rowClass, ExcelRowsConsumer<T> rowsConsumer, int headRowNumber) {
        if (Objects.isNull(multipartFile)) {
            log.error("错误：文件不存在");
            throw new BusinessException(ResponseCode.FAIL.getCode(), "文件不存在");
        }
        String extension = FilenameUtils.getExtension(multipartFile.getOriginalFilename());
        boolean isXls = "xls".equals(extension) || "xlsx".equals(extension);
        if (!isXls) {
            log.error("文件类型错误");
            throw new BusinessException(ResponseCode.FAIL.getCode(), "文件类型错误");
        }
        try {
            // headRowNumber(2) 自定义表头行数
            consumeExcelRows(multipartFile.getInputStream(), rowClass, rowsConsumer, headRowNumber);
        } catch (Exception e) {
            log.error("读取文件失败", e);
            throw new BusinessException(ResponseCode.FAIL.getCode(), e.getMessage());
        }
    }

    /**
     * 从输入流中消费行数据
     *
     * @param <T>           行对象泛型类
     * @param inputStream   输入流
     * @param rowClass      行对象类表示
     * @param rowsConsumer  记录行消费者
     * @param headRowNumber 表头大小控制，从第几行开始读数据
     */
    public static <T> void consumeExcelRows(InputStream inputStream, Class<T> rowClass, ExcelRowsConsumer<T> rowsConsumer, int headRowNumber) {
        EasyExcelFactory.read(inputStream, rowClass, new ExcelDataListener<>(rowsConsumer)).sheet().headRowNumber(headRowNumber).doRead();
    }

    /**
     * 将数据保存到Excel文件响应对象中
     *
     * @param response         HttpServletResponse
     * @param templateFilePath Excel 模板位置
     * @param fileName         文件流保存名称
     * @param rows             行记录列表
     * @param <T>              行对象泛型类
     */
    public static <T> void fillExcelRows(HttpServletResponse response, String templateFilePath, String fileName, List<T> rows) {
        fillExcelRows(response, templateFilePath, fileName, null, rows, DEFAULT_SHEET_SIZE, null, CustomHeadersHolder.builder().headers(null).build());
    }

    /**
     * 将数据保存到Excel文件响应对象中
     *
     * @param response HttpServletResponse
     * @param fileName 文件流保存名称
     * @param rowClass 行记录类对象
     * @param rows     行记录列表
     * @param <T>      行对象泛型类
     */
    public static <T> void fillExcelRows(HttpServletResponse response, String fileName, Class<T> rowClass, List<T> rows) {
        fillExcelRows(response, null, fileName, rowClass, rows, DEFAULT_SHEET_SIZE, null, CustomHeadersHolder.builder().headers(null).build());
    }

    /**
     * 将数据保存到Excel文件响应对象中
     *
     * @param response         HttpServletResponse
     * @param templateFilePath Excel 模板位置
     * @param fileName         文件流保存名称
     * @param rows             行记录列表
     * @param sheetSize        Excel 每页容量
     * @param <T>              行对象泛型类
     */
    public static <T> void fillExcelRows(HttpServletResponse response, String templateFilePath, String fileName, List<T> rows, int sheetSize) {
        fillExcelRows(response, templateFilePath, fileName, null, rows, sheetSize, null, CustomHeadersHolder.builder().headers(null).build());
    }

    /**
     * 将数据保存到Excel文件响应对象中
     *
     * @param response  HttpServletResponse
     * @param fileName  文件流保存名称
     * @param rowClass  行记录类对象
     * @param rows      行记录列表
     * @param sheetSize Excel 每页容量
     * @param <T>       行对象泛型类
     */
    public static <T> void fillExcelRows(HttpServletResponse response, String fileName, Class<T> rowClass, List<T> rows, int sheetSize) {
        fillExcelRows(response, null, fileName, rowClass, rows, sheetSize, null, CustomHeadersHolder.builder().headers(null).build());
    }

    /**
     * 将数据保存到Excel文件响应对象中
     *
     * @param response             HttpServletResponse
     * @param templateFilePath     Excel 模板位置
     * @param fileName             文件流保存名称
     * @param rows                 行记录列表
     * @param cellRangeAddressList 合并单元格区域列表
     * @param <T>                  行对象泛型类
     */

    public static <T> void fillExcelRows(HttpServletResponse response, String templateFilePath, String fileName, List<T> rows, List<CellRangeAddress> cellRangeAddressList) {
        fillExcelRows(response, templateFilePath, fileName, null, rows, DEFAULT_SHEET_SIZE, cellRangeAddressList, null);
    }

    /**
     * 将数据保存到Excel文件响应对象中
     *
     * @param response             HttpServletResponse
     * @param fileName             文件流保存名称
     * @param rowClass             行记录类对象
     * @param rows                 行记录列表
     * @param cellRangeAddressList 合并单元格区域列表
     * @param <T>                  行对象泛型类
     */
    public static <T> void fillExcelRows(HttpServletResponse response, String fileName, Class<T> rowClass, List<T> rows, List<CellRangeAddress> cellRangeAddressList) {
        fillExcelRows(response, null, fileName, rowClass, rows, DEFAULT_SHEET_SIZE, cellRangeAddressList, null);
    }

    /**
     * 将数据保存到Excel文件响应对象中
     *
     * @param response            HttpServletResponse
     * @param templateFilePath    Excel 模板位置
     * @param fileName            文件流保存名称
     * @param rows                行记录列表
     * @param customHeadersHolder 自定义的表头
     * @param <T>                 行对象泛型类
     */
    public static <T> void fillExcelRows(HttpServletResponse response, String templateFilePath, String fileName, List<T> rows, CustomHeadersHolder customHeadersHolder) {
        fillExcelRows(response, templateFilePath, fileName, null, rows, DEFAULT_SHEET_SIZE, null, customHeadersHolder);
    }

    /**
     * 将数据保存到Excel文件响应对象中
     *
     * @param response            HttpServletResponse
     * @param fileName            文件流保存名称
     * @param rowClass            行记录类对象
     * @param rows                行记录列表
     * @param customHeadersHolder 自定义的表头
     * @param <T>                 行对象泛型类
     */
    public static <T> void fillExcelRows(HttpServletResponse response, String fileName, Class<T> rowClass, List<T> rows, CustomHeadersHolder customHeadersHolder) {
        fillExcelRows(response, null, fileName, rowClass, rows, DEFAULT_SHEET_SIZE, null, customHeadersHolder);
    }

    /**
     * 将数据保存到Excel文件响应对象中
     *
     * @param response             HttpServletResponse
     * @param templateFilePath     Excel 模板位置
     * @param fileName             文件流保存名称
     * @param rows                 行记录列表
     * @param sheetSize            Excel 每页容量
     * @param cellRangeAddressList 合并单元格区域列表
     * @param <T>                  行对象泛型类
     */
    public static <T> void fillExcelRows(HttpServletResponse response, String templateFilePath, String fileName, List<T> rows, int sheetSize, List<CellRangeAddress> cellRangeAddressList) {
        fillExcelRows(response, templateFilePath, fileName, null, rows, sheetSize, cellRangeAddressList, null);
    }

    /**
     * 将数据保存到Excel文件响应对象中
     *
     * @param response             HttpServletResponse
     * @param fileName             文件流保存名称
     * @param rowClass             行记录类对象
     * @param rows                 行记录列表
     * @param sheetSize            Excel 每页容量
     * @param cellRangeAddressList 合并单元格区域列表
     * @param <T>                  行对象泛型类
     */
    public static <T> void fillExcelRows(HttpServletResponse response, String fileName, Class<T> rowClass, List<T> rows, int sheetSize, List<CellRangeAddress> cellRangeAddressList) {
        fillExcelRows(response, null, fileName, rowClass, rows, sheetSize, cellRangeAddressList, null);
    }

    /**
     * 将数据保存到Excel文件响应对象中
     *
     * @param response            HttpServletResponse
     * @param templateFilePath    Excel 模板位置
     * @param fileName            文件流保存名称
     * @param rows                行记录列表
     * @param sheetSize           Excel 每页容量
     * @param customHeadersHolder 自定义的表头
     * @param <T>                 行对象泛型类
     */
    public static <T> void fillExcelRows(HttpServletResponse response, String templateFilePath, String fileName, List<T> rows, int sheetSize, CustomHeadersHolder customHeadersHolder) {
        fillExcelRows(response, templateFilePath, fileName, null, rows, sheetSize, null, customHeadersHolder);
    }

    /**
     * 将数据保存到Excel文件响应对象中
     *
     * @param response            HttpServletResponse
     * @param fileName            文件流保存名称
     * @param rowClass            行记录类对象
     * @param rows                行记录列表
     * @param sheetSize           Excel 每页容量
     * @param customHeadersHolder 自定义的表头
     * @param <T>                 行对象泛型类
     */
    public static <T> void fillExcelRows(HttpServletResponse response, String fileName, Class<T> rowClass, List<T> rows, int sheetSize, CustomHeadersHolder customHeadersHolder) {
        fillExcelRows(response, null, fileName, rowClass, rows, sheetSize, null, customHeadersHolder);
    }

    /**
     * 将数据保存到Excel文件响应对象中
     *
     * @param response             HttpServletResponse
     * @param templateFilePath     Excel 模板位置
     * @param fileName             文件流保存名称
     * @param rows                 行记录列表
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     * @param <T>                  行对象泛型类
     */
    public static <T> void fillExcelRows(HttpServletResponse response, String templateFilePath, String fileName, List<T> rows, List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder) {
        fillExcelRows(response, templateFilePath, fileName, null, rows, DEFAULT_SHEET_SIZE, cellRangeAddressList, customHeadersHolder);
    }

    /**
     * 将数据保存到Excel文件响应对象中
     *
     * @param response             HttpServletResponse
     * @param fileName             文件流保存名称
     * @param rowClass             行记录类对象
     * @param rows                 行记录列表
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     * @param <T>                  行对象泛型类
     */
    public static <T> void fillExcelRows(HttpServletResponse response, String fileName, Class<T> rowClass, List<T> rows, List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder) {
        fillExcelRows(response, null, fileName, rowClass, rows, DEFAULT_SHEET_SIZE, cellRangeAddressList, customHeadersHolder);
    }

    /**
     * 将数据保存到Excel文件响应对象中
     *
     * @param response             HttpServletResponse
     * @param templateFilePath     Excel 模板位置
     * @param fileName             文件流保存名称
     * @param rows                 行记录列表
     * @param sheetSize            Excel 每页容量
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     * @param <T>                  行对象泛型类
     */
    public static <T> void fillExcelRows(HttpServletResponse response, String templateFilePath, String fileName, List<T> rows, int sheetSize, List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder) {
        fillExcelRows(response, templateFilePath, fileName, null, rows, sheetSize, cellRangeAddressList, customHeadersHolder);
    }

    /**
     * 将数据保存到Excel文件响应对象中
     *
     * @param response             HttpServletResponse
     * @param fileName             文件流保存名称
     * @param rowClass             行记录类对象
     * @param rows                 行记录列表
     * @param sheetSize            Excel 每页容量
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     * @param <T>                  行对象泛型类
     */
    public static <T> void fillExcelRows(HttpServletResponse response, String fileName, Class<T> rowClass, List<T> rows, int sheetSize, List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder) {
        fillExcelRows(response, null, fileName, rowClass, rows, sheetSize, cellRangeAddressList, customHeadersHolder);
    }

    /**
     * 将数据保存到Excel文件响应对象中
     *
     * @param response             HttpServletResponse
     * @param templateFilePath     Excel 模板位置
     * @param fileName             文件流保存名称
     * @param rowClass             行记录类对象
     * @param rows                 行记录列表
     * @param sheetSize            Excel 每页容量
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     * @param <T>                  行对象泛型类
     */
    @SuppressWarnings("java:S107")
    public static <T> void fillExcelRows(HttpServletResponse response, String templateFilePath, String fileName, Class<T> rowClass, List<T> rows, int sheetSize, List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder) {
        Map<String, List<T>> sheetMap = getSheetMap(rows, sheetSize);
        fillExcelRows(response, templateFilePath, fileName, rowClass, sheetMap, cellRangeAddressList, customHeadersHolder);
    }

    /**
     * 将数据保存到Excel文件响应对象中
     *
     * @param response             HttpServletResponse
     * @param templateFilePath     Excel 模板位置
     * @param fileName             文件流保存名称
     * @param rowClass             行对象类表示
     * @param map                  sheet和行记录列表的映射
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     * @param <T>                  行对象泛型类
     */
    public static <T> void fillExcelRows(HttpServletResponse response, String templateFilePath, String fileName, Class<T> rowClass, Map<String, List<T>> map, List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder) {
        try {
            OutputStream outputStream = prepareResponse(response, fileName);
            fillExcelRows(outputStream, templateFilePath, rowClass, map, cellRangeAddressList, customHeadersHolder);
        } catch (Exception e) {
            log.error("下载文件失败", e);
            printErrorMsg(response, e.getMessage());
        }
    }

    /**
     * 将数据保存到本地文件
     *
     * @param filePath         本地文件路径
     * @param templateFilePath Excel 模板位置
     * @param rows             行记录列表
     * @param <T>              行对象泛型类
     */
    public static <T> void fillExcelRows(String filePath, String templateFilePath, List<T> rows) {
        fillExcelRows(filePath, templateFilePath, null, rows, DEFAULT_SHEET_SIZE, null, null);
    }

    /**
     * 将数据保存到本地文件
     *
     * @param filePath 本地文件路径
     * @param rowClass 行记录类对象
     * @param rows     行记录列表
     * @param <T>      行对象泛型类
     */
    public static <T> void fillExcelRows(String filePath, Class<T> rowClass, List<T> rows) {
        fillExcelRows(filePath, null, rowClass, rows, DEFAULT_SHEET_SIZE, null, null);
    }

    /**
     * 将数据保存到本地文件
     *
     * @param filePath         本地文件路径
     * @param templateFilePath Excel 模板位置
     * @param rows             行记录列表
     * @param sheetSize        Excel 每页容量
     * @param <T>              行对象泛型类
     */
    public static <T> void fillExcelRows(String filePath, String templateFilePath, List<T> rows, int sheetSize) {
        fillExcelRows(filePath, templateFilePath, null, rows, sheetSize, null, null);
    }

    /**
     * 将数据保存到本地文件
     *
     * @param filePath  本地文件路径
     * @param rowClass  行记录类对象
     * @param rows      行记录列表
     * @param sheetSize Excel 每页容量
     * @param <T>       行对象泛型类
     */
    public static <T> void fillExcelRows(String filePath, Class<T> rowClass, List<T> rows, int sheetSize) {
        fillExcelRows(filePath, null, rowClass, rows, sheetSize, null, null);
    }

    /**
     * 将数据保存到本地文件
     *
     * @param filePath             本地文件路径
     * @param templateFilePath     Excel 模板位置
     * @param rows                 行记录列表
     * @param cellRangeAddressList 合并单元格区域列表
     * @param <T>                  行对象泛型类
     */
    public static <T> void fillExcelRows(String filePath, String templateFilePath, List<T> rows, List<CellRangeAddress> cellRangeAddressList) {
        fillExcelRows(filePath, templateFilePath, null, rows, DEFAULT_SHEET_SIZE, cellRangeAddressList, null);
    }

    /**
     * 将数据保存到本地文件
     *
     * @param filePath             本地文件路径
     * @param rowClass             行记录类对象
     * @param rows                 行记录列表
     * @param cellRangeAddressList 合并单元格区域列表
     * @param <T>                  行对象泛型类
     */
    public static <T> void fillExcelRows(String filePath, Class<T> rowClass, List<T> rows, List<CellRangeAddress> cellRangeAddressList) {
        fillExcelRows(filePath, null, rowClass, rows, DEFAULT_SHEET_SIZE, cellRangeAddressList, null);
    }

    /**
     * 将数据保存到本地文件
     *
     * @param filePath            本地文件路径
     * @param templateFilePath    Excel 模板位置
     * @param rows                行记录列表
     * @param customHeadersHolder 自定义的表头
     * @param <T>                 行对象泛型类
     */
    public static <T> void fillExcelRows(String filePath, String templateFilePath, List<T> rows, CustomHeadersHolder customHeadersHolder) {
        fillExcelRows(filePath, templateFilePath, null, rows, DEFAULT_SHEET_SIZE, null, customHeadersHolder);
    }

    /**
     * 将数据保存到本地文件
     *
     * @param filePath            本地文件路径
     * @param rowClass            行记录类对象
     * @param rows                行记录列表
     * @param customHeadersHolder 自定义的表头
     * @param <T>                 行对象泛型类
     */
    public static <T> void fillExcelRows(String filePath, Class<T> rowClass, List<T> rows, CustomHeadersHolder customHeadersHolder) {
        fillExcelRows(filePath, null, rowClass, rows, DEFAULT_SHEET_SIZE, null, customHeadersHolder);
    }

    /**
     * 将数据保存到本地文件
     *
     * @param filePath             本地文件路径
     * @param templateFilePath     Excel 模板位置
     * @param rows                 行记录列表
     * @param sheetSize            Excel 每页容量
     * @param cellRangeAddressList 合并单元格区域列表
     * @param <T>                  行对象泛型类
     */
    public static <T> void fillExcelRows(String filePath, String templateFilePath, List<T> rows, int sheetSize, List<CellRangeAddress> cellRangeAddressList) {
        fillExcelRows(filePath, templateFilePath, null, rows, sheetSize, cellRangeAddressList, null);
    }

    /**
     * 将数据保存到本地文件
     *
     * @param filePath             本地文件路径
     * @param rowClass             行记录类对象
     * @param rows                 行记录列表
     * @param sheetSize            Excel 每页容量
     * @param cellRangeAddressList 合并单元格区域列表
     * @param <T>                  行对象泛型类
     */
    public static <T> void fillExcelRows(String filePath, Class<T> rowClass, List<T> rows, int sheetSize, List<CellRangeAddress> cellRangeAddressList) {
        fillExcelRows(filePath, null, rowClass, rows, sheetSize, cellRangeAddressList, null);
    }

    /**
     * 将数据保存到本地文件
     *
     * @param filePath            本地文件路径
     * @param templateFilePath    Excel 模板位置
     * @param rows                行记录列表
     * @param sheetSize           Excel 每页容量
     * @param customHeadersHolder 自定义的表头
     * @param <T>                 行对象泛型类
     */
    public static <T> void fillExcelRows(String filePath, String templateFilePath, List<T> rows, int sheetSize, CustomHeadersHolder customHeadersHolder) {
        fillExcelRows(filePath, templateFilePath, null, rows, sheetSize, null, customHeadersHolder);
    }

    /**
     * 将数据保存到本地文件
     *
     * @param filePath            本地文件路径
     * @param rowClass            行记录类对象
     * @param rows                行记录列表
     * @param sheetSize           Excel 每页容量
     * @param customHeadersHolder 自定义的表头
     * @param <T>                 行对象泛型类
     */
    public static <T> void fillExcelRows(String filePath, Class<T> rowClass, List<T> rows, int sheetSize, CustomHeadersHolder customHeadersHolder) {
        fillExcelRows(filePath, null, rowClass, rows, sheetSize, null, customHeadersHolder);
    }

    /**
     * 将数据保存到本地文件
     *
     * @param filePath             本地文件路径
     * @param templateFilePath     Excel 模板位置
     * @param rows                 行记录列表
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     * @param <T>                  行对象泛型类
     */
    public static <T> void fillExcelRows(String filePath, String templateFilePath, List<T> rows, List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder) {
        fillExcelRows(filePath, templateFilePath, null, rows, DEFAULT_SHEET_SIZE, cellRangeAddressList, customHeadersHolder);
    }

    /**
     * 将数据保存到本地文件
     *
     * @param filePath             本地文件路径
     * @param rowClass             行记录类对象
     * @param rows                 行记录列表
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     * @param <T>                  行对象泛型类
     */
    public static <T> void fillExcelRows(String filePath, Class<T> rowClass, List<T> rows, List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder) {
        fillExcelRows(filePath, null, rowClass, rows, DEFAULT_SHEET_SIZE, cellRangeAddressList, customHeadersHolder);
    }

    /**
     * 将数据保存到本地文件
     *
     * @param filePath             本地文件路径
     * @param templateFilePath     Excel 模板位置
     * @param rows                 行记录列表
     * @param sheetSize            Excel 每页容量
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     * @param <T>                  行对象泛型类
     */
    public static <T> void fillExcelRows(String filePath, String templateFilePath, List<T> rows, int sheetSize, List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder) {
        fillExcelRows(filePath, templateFilePath, null, rows, sheetSize, cellRangeAddressList, customHeadersHolder);
    }

    /**
     * 将数据保存到本地文件
     *
     * @param filePath             本地文件路径
     * @param rowClass             行记录类对象
     * @param rows                 行记录列表
     * @param sheetSize            Excel 每页容量
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     * @param <T>                  行对象泛型类
     */
    public static <T> void fillExcelRows(String filePath, Class<T> rowClass, List<T> rows, int sheetSize, List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder) {
        fillExcelRows(filePath, null, rowClass, rows, sheetSize, cellRangeAddressList, customHeadersHolder);
    }

    /**
     * 将数据保存到本地文件
     *
     * @param filePath             本地文件路径
     * @param templateFilePath     Excel 模板位置
     * @param rowClass             行记录类对象
     * @param rows                 行记录列表
     * @param sheetSize            Excel 每页容量
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     * @param <T>                  行对象泛型类
     */
    public static <T> void fillExcelRows(String filePath, String templateFilePath, Class<T> rowClass, List<T> rows, int sheetSize, List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder) {
        Map<String, List<T>> sheetMap = getSheetMap(rows, sheetSize);
        fillExcelRows(filePath, templateFilePath, rowClass, sheetMap, cellRangeAddressList, customHeadersHolder);
    }

    /**
     * 将数据保存到本地文件
     *
     * @param filePath             本地文件路径
     * @param templateFilePath     Excel 模板位置
     * @param rowClass             行对象类表示
     * @param map                  sheet和行记录列表的映射
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     * @param <T>                  行对象泛型类
     */
    public static <T> void fillExcelRows(String filePath, String templateFilePath, Class<T> rowClass, Map<String, List<T>> map, List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder) {
        File file = new File(filePath);
        try {
            fillExcelRows(new FileOutputStream(file), templateFilePath, rowClass, map, cellRangeAddressList, customHeadersHolder);
        } catch (FileNotFoundException e) {
            log.error("文件不存在", e);
        }
    }

    @Slf4j
    public static class ExcelDataListener<T> extends AnalysisEventListener<T> {

        private final ObjectMapper objectMapper = new ObjectMapper();

        private final ExcelRowsConsumer<T> excelRowsConsumer;

        /**
         * 每隔100条存储数据库，然后清理 list ，方便内存回收
         */
        private static final int BATCH_COUNT = 100;

        private final List<T> list = new ArrayList<>();

        public ExcelDataListener(ExcelRowsConsumer<T> excelRowsConsumer) {
            this.excelRowsConsumer = excelRowsConsumer;
        }

        @SneakyThrows
        @Override
        public void invoke(T data, AnalysisContext context) {
            int currentRowIndex = context.readRowHolder().getRowIndex() + 1;
            log.info("解析到一条数据:(行号: {}) {} ", currentRowIndex, objectMapper.writeValueAsString(data));
            list.add(data);
            if (list.size() >= BATCH_COUNT) {
                consumeData(currentRowIndex - list.size() + 1, currentRowIndex);
                list.clear();
            }
        }

        @Override
        public void doAfterAllAnalysed(AnalysisContext context) {
            int currentRowIndex = context.readRowHolder().getRowIndex() + 1;
            if (!list.isEmpty()) {
                consumeData(currentRowIndex - list.size() + 1, currentRowIndex);
            }
            log.info("所有数据解析完成！");
        }

        private void consumeData(int startRowIndex, int endRowIndex) {
            log.info("{}条数据，从第{}行到第{}行，开始存储数据库！", list.size(), startRowIndex, endRowIndex);
            excelRowsConsumer.consume(list, startRowIndex, endRowIndex);
            log.info("存储数据库成功！");
        }
    }

    @FunctionalInterface
    public interface ExcelRowsConsumer<T> {

        /**
         * 消费Excel行列表
         *
         * @param rows          行列表
         * @param startRowIndex 起始行号,从 1 开始
         * @param endRowIndex   结束行号
         */
        void consume(List<T> rows, int startRowIndex, int endRowIndex);
    }

    public static <T> void fillExcelRows(OutputStream outputStream, String templateFilePath, Class<T> rowClass, Map<String, List<T>> map, List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder) {
        ExcelWriterBuilder excelWriterBuilder = EasyExcelFactory.write(outputStream, rowClass)
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                .registerWriteHandler(new MultiRangeAddressMergeStrategy(cellRangeAddressList));
        ExcelWriter excelWriter = null;
        try {
            List<List<String>> headers = Optional.ofNullable(customHeadersHolder).map(CustomHeadersHolder::getHeaders).orElse(null);
            if (templateFilePath != null && !templateFilePath.isEmpty()) {
                // 有模板文件使用fill方法
                excelWriterBuilder.withTemplate(templateFilePath);
                excelWriter = excelWriterBuilder.build();
                FillConfig fillConfig = FillConfig.builder().forceNewRow(Boolean.TRUE).build();
                ExcelWriter finalExcelWriter = excelWriter;
                map.forEach((sheetName, rows) -> {
                    WriteSheet writeSheet = EasyExcelFactory.writerSheet().head(headers).sheetName(sheetName).build();
                    finalExcelWriter.fill(rows, fillConfig, writeSheet);
                });
                return;
            }
            // 没有模板文件使用write方法
            excelWriter = excelWriterBuilder.build();
            ExcelWriter finalExcelWriter = excelWriter;
            map.forEach((sheetName, rows) -> {
                WriteSheet writeSheet = EasyExcelFactory.writerSheet().head(headers).sheetName(sheetName).build();
                finalExcelWriter.write(rows, writeSheet);
            });
        } catch (Exception e) {
            log.error(COMMON_ERROR_MSG, e);
        } finally {
            // 关闭流
            if (excelWriter != null) {
                excelWriter.finish();
                excelWriter.close();
            }
        }
    }


    // region 自动异步分批获取数据，填充Excel行数据

    /**
     * 自动异步分批获取数据，填充Excel行数据
     *
     * @param <T>           行对象泛型
     * @param response      HttpServletResponse
     * @param fileName      文件名称
     * @param total         总数
     * @param batchSize     分批大小
     * @param batchSupplier 分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass      行对象类
     */
    public static <T> void asyncBatchFillExcelRows(HttpServletResponse response, String fileName, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass) {
        asyncBatchFillExcelRows(response, null, fileName, total, batchSize, batchSupplier, rowClass, null, null, 5);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据
     *
     * @param <T>              行对象泛型
     * @param response         HttpServletResponse
     * @param templateFilePath Excel 模板路径
     * @param fileName         文件名称
     * @param total            总数
     * @param batchSize        分批大小
     * @param batchSupplier    分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass         行对象类
     */
    public static <T> void asyncBatchFillExcelRows(HttpServletResponse response, String templateFilePath, String fileName, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass) {
        asyncBatchFillExcelRows(response, templateFilePath, fileName, total, batchSize, batchSupplier, rowClass, null, null, 5);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据
     *
     * @param <T>                  行对象泛型
     * @param response             HttpServletResponse
     * @param fileName             文件名称
     * @param total                总数
     * @param batchSize            分批大小
     * @param batchSupplier        分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass             行对象类
     * @param cellRangeAddressList 合并单元格区域列表
     */
    public static <T> void asyncBatchFillExcelRows(HttpServletResponse response, String fileName, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   List<CellRangeAddress> cellRangeAddressList) {
        asyncBatchFillExcelRows(response, null, fileName, total, batchSize, batchSupplier, rowClass, cellRangeAddressList, null, 5);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据
     *
     * @param <T>                 行对象泛型
     * @param response            HttpServletResponse
     * @param fileName            文件名称
     * @param total               总数
     * @param batchSize           分批大小
     * @param batchSupplier       分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass            行对象类
     * @param customHeadersHolder 自定义的表头
     */
    public static <T> void asyncBatchFillExcelRows(HttpServletResponse response, String fileName, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   CustomHeadersHolder customHeadersHolder) {
        asyncBatchFillExcelRows(response, null, fileName, total, batchSize, batchSupplier, rowClass, null, customHeadersHolder, 5);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据
     *
     * @param <T>           行对象泛型
     * @param response      HttpServletResponse
     * @param fileName      文件名称
     * @param total         总数
     * @param batchSize     分批大小
     * @param batchSupplier 分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass      行对象类
     * @param nThreads      并发线程数
     */
    public static <T> void asyncBatchFillExcelRows(HttpServletResponse response, String fileName, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   int nThreads) {
        asyncBatchFillExcelRows(response, null, fileName, total, batchSize, batchSupplier, rowClass, null, null, nThreads);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据
     *
     * @param <T>                  行对象泛型
     * @param response             HttpServletResponse
     * @param templateFilePath     Excel 模板路径
     * @param fileName             文件名称
     * @param total                总数
     * @param batchSize            分批大小
     * @param batchSupplier        分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass             行对象类
     * @param cellRangeAddressList 合并单元格区域列表
     */
    public static <T> void asyncBatchFillExcelRows(HttpServletResponse response, String templateFilePath, String fileName, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   List<CellRangeAddress> cellRangeAddressList) {
        asyncBatchFillExcelRows(response, templateFilePath, fileName, total, batchSize, batchSupplier, rowClass, cellRangeAddressList, null, 5);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据
     *
     * @param <T>                 行对象泛型
     * @param response            HttpServletResponse
     * @param templateFilePath    Excel 模板路径
     * @param fileName            文件名称
     * @param total               总数
     * @param batchSize           分批大小
     * @param batchSupplier       分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass            行对象类
     * @param customHeadersHolder 自定义的表头
     */
    public static <T> void asyncBatchFillExcelRows(HttpServletResponse response, String templateFilePath, String fileName, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   CustomHeadersHolder customHeadersHolder) {
        asyncBatchFillExcelRows(response, templateFilePath, fileName, total, batchSize, batchSupplier, rowClass, null, customHeadersHolder, 5);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据
     *
     * @param <T>              行对象泛型
     * @param response         HttpServletResponse
     * @param templateFilePath Excel 模板路径
     * @param fileName         文件名称
     * @param total            总数
     * @param batchSize        分批大小
     * @param batchSupplier    分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass         行对象类
     * @param nThreads         并发线程数
     */
    public static <T> void asyncBatchFillExcelRows(HttpServletResponse response, String templateFilePath, String fileName, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   int nThreads) {
        asyncBatchFillExcelRows(response, templateFilePath, fileName, total, batchSize, batchSupplier, rowClass, null, null, nThreads);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据
     *
     * @param <T>                  行对象泛型
     * @param response             HttpServletResponse
     * @param fileName             文件名称
     * @param total                总数
     * @param batchSize            分批大小
     * @param batchSupplier        分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass             行对象类
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     */
    public static <T> void asyncBatchFillExcelRows(HttpServletResponse response, String fileName, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder) {
        asyncBatchFillExcelRows(response, null, fileName, total, batchSize, batchSupplier, rowClass, cellRangeAddressList, customHeadersHolder, 5);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据
     *
     * @param <T>                  行对象泛型
     * @param response             HttpServletResponse
     * @param fileName             文件名称
     * @param total                总数
     * @param batchSize            分批大小
     * @param batchSupplier        分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass             行对象类
     * @param cellRangeAddressList 合并单元格区域列表
     * @param nThreads             并发线程数
     */
    public static <T> void asyncBatchFillExcelRows(HttpServletResponse response, String fileName, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   List<CellRangeAddress> cellRangeAddressList, int nThreads) {
        asyncBatchFillExcelRows(response, null, fileName, total, batchSize, batchSupplier, rowClass, cellRangeAddressList, null, nThreads);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据
     *
     * @param <T>                 行对象泛型
     * @param response            HttpServletResponse
     * @param fileName            文件名称
     * @param total               总数
     * @param batchSize           分批大小
     * @param batchSupplier       分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass            行对象类
     * @param customHeadersHolder 自定义的表头
     * @param nThreads            并发线程数
     */
    public static <T> void asyncBatchFillExcelRows(HttpServletResponse response, String fileName, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   CustomHeadersHolder customHeadersHolder, int nThreads) {
        asyncBatchFillExcelRows(response, null, fileName, total, batchSize, batchSupplier, rowClass, null, customHeadersHolder, nThreads);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据
     *
     * @param <T>                  行对象泛型
     * @param response             HttpServletResponse
     * @param templateFilePath     Excel 模板路径
     * @param fileName             文件名称
     * @param total                总数
     * @param batchSize            分批大小
     * @param batchSupplier        分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass             行对象类
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     */
    public static <T> void asyncBatchFillExcelRows(HttpServletResponse response, String templateFilePath, String fileName, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder) {
        asyncBatchFillExcelRows(response, templateFilePath, fileName, total, batchSize, batchSupplier, rowClass, cellRangeAddressList, customHeadersHolder, 5);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据
     *
     * @param <T>                  行对象泛型
     * @param response             HttpServletResponse
     * @param templateFilePath     Excel 模板路径
     * @param fileName             文件名称
     * @param total                总数
     * @param batchSize            分批大小
     * @param batchSupplier        分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass             行对象类
     * @param cellRangeAddressList 合并单元格区域列表
     * @param nThreads             并发线程数
     */
    public static <T> void asyncBatchFillExcelRows(HttpServletResponse response, String templateFilePath, String fileName, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   List<CellRangeAddress> cellRangeAddressList, int nThreads) {
        asyncBatchFillExcelRows(response, templateFilePath, fileName, total, batchSize, batchSupplier, rowClass, cellRangeAddressList, null, nThreads);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据
     *
     * @param <T>                 行对象泛型
     * @param response            HttpServletResponse
     * @param templateFilePath    Excel 模板路径
     * @param fileName            文件名称
     * @param total               总数
     * @param batchSize           分批大小
     * @param batchSupplier       分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass            行对象类
     * @param customHeadersHolder 自定义的表头
     * @param nThreads            并发线程数
     */
    public static <T> void asyncBatchFillExcelRows(HttpServletResponse response, String templateFilePath, String fileName, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   CustomHeadersHolder customHeadersHolder, int nThreads) {
        asyncBatchFillExcelRows(response, templateFilePath, fileName, total, batchSize, batchSupplier, rowClass, null, customHeadersHolder, nThreads);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据
     *
     * @param <T>                  行对象泛型
     * @param response             HttpServletResponse
     * @param fileName             文件名称
     * @param total                总数
     * @param batchSize            分批大小
     * @param batchSupplier        分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass             行对象类
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     * @param nThreads             并发线程数
     */
    public static <T> void asyncBatchFillExcelRows(HttpServletResponse response, String fileName,
                                                   int total, int batchSize, BiFunction<Integer, Integer, List<T>> batchSupplier,
                                                   Class<T> rowClass, List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder, int nThreads) {
        asyncBatchFillExcelRows(response, null, fileName, total, batchSize, batchSupplier, rowClass, cellRangeAddressList, customHeadersHolder, nThreads);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据
     *
     * @param <T>                  行对象泛型
     * @param response             HttpServletResponse
     * @param templateFilePath     Excel 模板路径
     * @param fileName             文件名称
     * @param total                总数
     * @param batchSize            分批大小
     * @param batchSupplier        分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass             行对象类
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     * @param nThreads             并发线程数
     */
    public static <T> void asyncBatchFillExcelRows(HttpServletResponse response, String templateFilePath, String fileName,
                                                   int total, int batchSize, BiFunction<Integer, Integer, List<T>> batchSupplier,
                                                   Class<T> rowClass, List<CellRangeAddress> cellRangeAddressList,
                                                   CustomHeadersHolder customHeadersHolder, int nThreads) {
        OutputStream outputStream = prepareResponse(response, fileName);
        asyncBatchFillExcelRows(outputStream, templateFilePath, fileName, total, batchSize, batchSupplier, rowClass, cellRangeAddressList, customHeadersHolder, nThreads);
    }


    private static OutputStream prepareResponse(HttpServletResponse response, String fileName) {
        OutputStream outputStream;
        try {
            outputStream = response.getOutputStream();
        } catch (IOException e) {
            throw new IllegalStateException("获取输出流失败", e);
        }
        // 设置响应头
        response.setContentType("application/vnd.ms-excel");
        response.setCharacterEncoding("utf-8");
        try {
            response.setHeader("Content-disposition", "attachment;filename*=utf-8''" + URLEncoder.encode(fileName, "UTF-8").replace("\\+", "%20") + ".xlsx");
        } catch (UnsupportedEncodingException e) {
            throw new IllegalStateException("设置响应头失败", e);
        }
        return outputStream;
    }

    private static void printErrorMsg(HttpServletResponse response, String e) {
        response.reset();
        response.setCharacterEncoding("UTF-8");
        response.setContentType("application/json;charset=utf-8");
        response.setStatus(500);
        PrintWriter writer;
        try {
            writer = response.getWriter();
        } catch (IOException ex) {
            throw new IllegalStateException("获取输出流失败", ex);
        }
        writer.write("{\"code\":0,\"message\":\"" + ("下载文件失败" + ": " + e) + "\"}");
        writer.flush();
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据，并保存到本地文件
     *
     * @param <T>           行对象泛型
     * @param filePath      本地文件路径
     * @param total         总数
     * @param batchSize     分批大小
     * @param batchSupplier 分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass      行对象类
     */
    public static <T> void asyncBatchFillExcelRows(String filePath, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass) {
        asyncBatchFillExcelRows(filePath, null, total, batchSize, batchSupplier, rowClass, null, null, 5);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据，并保存到本地文件
     *
     * @param <T>              行对象泛型
     * @param filePath         本地文件路径
     * @param templateFilePath Excel 模板路径
     * @param total            总数
     * @param batchSize        分批大小
     * @param batchSupplier    分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass         行对象类
     */
    public static <T> void asyncBatchFillExcelRows(String filePath, String templateFilePath, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass) {
        asyncBatchFillExcelRows(filePath, templateFilePath, total, batchSize, batchSupplier, rowClass, null, null, 5);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据，并保存到本地文件
     *
     * @param <T>                  行对象泛型
     * @param filePath             本地文件路径
     * @param total                总数
     * @param batchSize            分批大小
     * @param batchSupplier        分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass             行对象类
     * @param cellRangeAddressList 合并单元格区域列表
     */
    public static <T> void asyncBatchFillExcelRows(String filePath, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   List<CellRangeAddress> cellRangeAddressList) {
        asyncBatchFillExcelRows(filePath, null, total, batchSize, batchSupplier, rowClass, cellRangeAddressList, null, 5);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据，并保存到本地文件
     *
     * @param <T>                 行对象泛型
     * @param filePath            本地文件路径
     * @param total               总数
     * @param batchSize           分批大小
     * @param batchSupplier       分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass            行对象类
     * @param customHeadersHolder 自定义的表头
     */
    public static <T> void asyncBatchFillExcelRows(String filePath, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   CustomHeadersHolder customHeadersHolder) {
        asyncBatchFillExcelRows(filePath, null, total, batchSize, batchSupplier, rowClass, null, customHeadersHolder, 5);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据，并保存到本地文件
     *
     * @param <T>           行对象泛型
     * @param filePath      本地文件路径
     * @param total         总数
     * @param batchSize     分批大小
     * @param batchSupplier 分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass      行对象类
     * @param nThreads      并发线程数
     */
    public static <T> void asyncBatchFillExcelRows(String filePath, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   int nThreads) {
        asyncBatchFillExcelRows(filePath, null, total, batchSize, batchSupplier, rowClass, null, null, nThreads);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据，并保存到本地文件
     *
     * @param <T>                  行对象泛型
     * @param filePath             本地文件路径
     * @param templateFilePath     Excel 模板路径
     * @param total                总数
     * @param batchSize            分批大小
     * @param batchSupplier        分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass             行对象类
     * @param cellRangeAddressList 合并单元格区域列表
     */
    public static <T> void asyncBatchFillExcelRows(String filePath, String templateFilePath, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   List<CellRangeAddress> cellRangeAddressList) {
        asyncBatchFillExcelRows(filePath, templateFilePath, total, batchSize, batchSupplier, rowClass, cellRangeAddressList, null, 5);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据，并保存到本地文件
     *
     * @param <T>                 行对象泛型
     * @param filePath            本地文件路径
     * @param templateFilePath    Excel 模板路径
     * @param total               总数
     * @param batchSize           分批大小
     * @param batchSupplier       分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass            行对象类
     * @param customHeadersHolder 自定义的表头
     */
    public static <T> void asyncBatchFillExcelRows(String filePath, String templateFilePath, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   CustomHeadersHolder customHeadersHolder) {
        asyncBatchFillExcelRows(filePath, templateFilePath, total, batchSize, batchSupplier, rowClass, null, customHeadersHolder, 5);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据，并保存到本地文件
     *
     * @param <T>              行对象泛型
     * @param filePath         本地文件路径
     * @param templateFilePath Excel 模板路径
     * @param total            总数
     * @param batchSize        分批大小
     * @param batchSupplier    分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass         行对象类
     * @param nThreads         并发线程数
     */
    public static <T> void asyncBatchFillExcelRows(String filePath, String templateFilePath, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   int nThreads) {
        asyncBatchFillExcelRows(filePath, templateFilePath, total, batchSize, batchSupplier, rowClass, null, null, nThreads);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据，并保存到本地文件
     *
     * @param <T>                  行对象泛型
     * @param filePath             本地文件路径
     * @param total                总数
     * @param batchSize            分批大小
     * @param batchSupplier        分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass             行对象类
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     */
    public static <T> void asyncBatchFillExcelRows(String filePath, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder) {
        asyncBatchFillExcelRows(filePath, null, total, batchSize, batchSupplier, rowClass, cellRangeAddressList, customHeadersHolder, 5);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据，并保存到本地文件
     *
     * @param <T>                  行对象泛型
     * @param filePath             本地文件路径
     * @param total                总数
     * @param batchSize            分批大小
     * @param batchSupplier        分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass             行对象类
     * @param cellRangeAddressList 合并单元格区域列表
     * @param nThreads             并发线程数
     */
    public static <T> void asyncBatchFillExcelRows(String filePath, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   List<CellRangeAddress> cellRangeAddressList, int nThreads) {
        asyncBatchFillExcelRows(filePath, null, total, batchSize, batchSupplier, rowClass, cellRangeAddressList, null, nThreads);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据，并保存到本地文件
     *
     * @param <T>                 行对象泛型
     * @param filePath            本地文件路径
     * @param total               总数
     * @param batchSize           分批大小
     * @param batchSupplier       分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass            行对象类
     * @param customHeadersHolder 自定义的表头
     * @param nThreads            并发线程数
     */
    public static <T> void asyncBatchFillExcelRows(String filePath, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   CustomHeadersHolder customHeadersHolder, int nThreads) {
        asyncBatchFillExcelRows(filePath, null, total, batchSize, batchSupplier, rowClass, null, customHeadersHolder, nThreads);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据，并保存到本地文件
     *
     * @param <T>                  行对象泛型
     * @param filePath             本地文件路径
     * @param templateFilePath     Excel 模板路径
     * @param total                总数
     * @param batchSize            分批大小
     * @param batchSupplier        分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass             行对象类
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     */
    public static <T> void asyncBatchFillExcelRows(String filePath, String templateFilePath, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder) {
        asyncBatchFillExcelRows(filePath, templateFilePath, total, batchSize, batchSupplier, rowClass, cellRangeAddressList, customHeadersHolder, 5);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据，并保存到本地文件
     *
     * @param <T>                  行对象泛型
     * @param filePath             本地文件路径
     * @param templateFilePath     Excel 模板路径
     * @param total                总数
     * @param batchSize            分批大小
     * @param batchSupplier        分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass             行对象类
     * @param cellRangeAddressList 合并单元格区域列表
     * @param nThreads             并发线程数
     */
    public static <T> void asyncBatchFillExcelRows(String filePath, String templateFilePath, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   List<CellRangeAddress> cellRangeAddressList, int nThreads) {
        asyncBatchFillExcelRows(filePath, templateFilePath, total, batchSize, batchSupplier, rowClass, cellRangeAddressList, null, nThreads);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据，并保存到本地文件
     *
     * @param <T>                 行对象泛型
     * @param filePath            本地文件路径
     * @param templateFilePath    Excel 模板路径
     * @param total               总数
     * @param batchSize           分批大小
     * @param batchSupplier       分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass            行对象类
     * @param customHeadersHolder 自定义的表头
     * @param nThreads            并发线程数
     */
    public static <T> void asyncBatchFillExcelRows(String filePath, String templateFilePath, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   CustomHeadersHolder customHeadersHolder, int nThreads) {
        asyncBatchFillExcelRows(filePath, templateFilePath, total, batchSize, batchSupplier, rowClass, null, customHeadersHolder, nThreads);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据，并保存到本地文件
     *
     * @param <T>                  行对象泛型
     * @param filePath             本地文件路径
     * @param total                总数
     * @param batchSize            分批大小
     * @param batchSupplier        分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass             行对象类
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     * @param nThreads             并发线程数
     */
    public static <T> void asyncBatchFillExcelRows(String filePath, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder,
                                                   int nThreads) {
        asyncBatchFillExcelRows(filePath, null, total, batchSize, batchSupplier, rowClass, cellRangeAddressList, customHeadersHolder, nThreads);
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据，并保存到本地文件
     *
     * @param <T>                  行对象泛型
     * @param filePath             本地文件路径
     * @param templateFilePath     Excel 模板路径
     * @param total                总数
     * @param batchSize            分批大小
     * @param batchSupplier        分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass             行对象类
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     * @param nThreads             并发线程数
     */
    public static <T> void asyncBatchFillExcelRows(String filePath, String templateFilePath, int total, int batchSize,
                                                   BiFunction<Integer, Integer, List<T>> batchSupplier, Class<T> rowClass,
                                                   List<CellRangeAddress> cellRangeAddressList, CustomHeadersHolder customHeadersHolder,
                                                   int nThreads) {
        try (OutputStream outputStream = Files.newOutputStream(Paths.get(filePath))) {
            asyncBatchFillExcelRows(outputStream, templateFilePath, filePath, total, batchSize, batchSupplier, rowClass, cellRangeAddressList, customHeadersHolder, nThreads);
        } catch (IOException e) {
            log.error(COMMON_ERROR_MSG, e);
        }
    }

    /**
     * 自动异步分批获取数据，填充Excel行数据
     *
     * @param <T>                  行对象泛型
     * @param outputStream         输出流
     * @param templateFilePath     Excel 模板路径
     * @param fileName             文件名称
     * @param total                总数
     * @param batchSize            分批大小
     * @param batchSupplier        分批数据获取函数，接受批次号和每批大小，返回分批数据
     * @param rowClass             行对象类
     * @param cellRangeAddressList 合并单元格区域列表
     * @param customHeadersHolder  自定义的表头
     * @param nThreads             并发线程数
     */
    public static <T> void asyncBatchFillExcelRows(OutputStream outputStream, String templateFilePath, String fileName,
                                                   int total, int batchSize, BiFunction<Integer, Integer, List<T>> batchSupplier,
                                                   Class<T> rowClass, List<CellRangeAddress> cellRangeAddressList,
                                                   CustomHeadersHolder customHeadersHolder, int nThreads) {
        // 定义分页参数
        int totalBatchCount = (total + batchSize - 1) / batchSize;
        List<CompletableFuture<List<T>>> futures = new ArrayList<>();

        // 创建线程池
        ExecutorService executorService = Executors.newFixedThreadPool(nThreads);

        // 异步获取分页数据
        for (int i = 1; i <= totalBatchCount; i++) {
            int currentBatchNum = i;
            CompletableFuture<List<T>> future = CompletableFuture.supplyAsync(() -> batchSupplier.apply(currentBatchNum, batchSize), executorService);
            futures.add(future);
        }

        // 创建 EasyExcel 写入器
        ExcelWriterBuilder writerBuilder = EasyExcelFactory.write(outputStream, rowClass)
                .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
                .registerWriteHandler(new MultiRangeAddressMergeStrategy(cellRangeAddressList));
        if (templateFilePath != null && !templateFilePath.isEmpty()) {
            writerBuilder.withTemplate(templateFilePath);
        }

        try (ExcelWriter excelWriter = writerBuilder.build()) {
            List<List<String>> headers = Optional.ofNullable(customHeadersHolder).map(CustomHeadersHolder::getHeaders).orElse(null);
            WriteSheet writeSheet = EasyExcelFactory.writerSheet(fileName).head(headers).build();

            // 收集所有分页数据并写入 Excel
            for (CompletableFuture<List<T>> future : futures) {
                List<T> data = future.get();
                if (templateFilePath != null && !templateFilePath.isEmpty()) {
                    excelWriter.fill(data, writeSheet);
                } else {
                    excelWriter.write(data, writeSheet);
                }
            }
        } catch (ExecutionException e) {
            log.error(COMMON_ERROR_MSG, e);
        } catch (InterruptedException e) {
            log.warn("Interrupted!", e);
            Thread.currentThread().interrupt();
        } finally {
            executorService.shutdown();
        }
    }
    // endregion 自动异步分批获取数据，填充Excel行数据

    /**
     * 根据合并区域列表合并单元格
     *
     * @author zhaowenhao
     * @since 2020-12-27
     */
    public static class MultiRangeAddressMergeStrategy implements SheetWriteHandler {

        private final List<CellRangeAddress> cellRangeAddressList;

        public MultiRangeAddressMergeStrategy(List<CellRangeAddress> cellRangeAddressList) {
            this.cellRangeAddressList = cellRangeAddressList;
        }

        @Override
        public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
            if (!CollUtil.isEmpty(cellRangeAddressList)) {
                for (CellRangeAddress cellRangeAddress : cellRangeAddressList) {
                    writeSheetHolder.getSheet().addMergedRegion(cellRangeAddress);
                }
            }
        }

    }

    private static <T> Map<String, List<T>> getSheetMap(List<T> rows, int sheetSize) {
        Map<String, List<T>> sheetMap = new HashMap<>(5);
        int sheetCount = rows.size() % sheetSize == 0 ? (rows.size() / sheetSize) : (rows.size() / sheetSize) + 1;
        for (int i = 0; i < sheetCount; i++) {
            int sheetNum = i + 1;
            List<T> tempList = rows.subList(i * sheetSize, Math.min(sheetNum * sheetSize, rows.size()));
            sheetMap.put("sheet" + sheetNum, tempList);
        }
        return sheetMap;
    }

    /**
     * 动态根据内容设置列宽，复制自：
     * {@link com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy} ，
     * 结合测试结果，对列宽的计算进行了调整，使其更符合实际情况。
     */
    public static class LongestMatchColumnWidthStyleStrategy extends AbstractColumnWidthStyleStrategy {

        private static final int MAX_COLUMN_WIDTH = 255;
        public static final int DEFAULT_DATE_LENGTH = 25;
        public static final int DEFAULT_DATA_LENGTH = 30;

        private final Map<Integer, Map<Integer, Integer>> cache = MapUtils.newHashMapWithExpectedSize(8);

        @Override
        protected void setColumnWidth(WriteSheetHolder writeSheetHolder, List<WriteCellData<?>> cellDataList, Cell cell,
                                      Head head,
                                      Integer relativeRowIndex, Boolean isHead) {
            boolean needSetWidth = isHead || !CollectionUtils.isEmpty(cellDataList);
            if (!needSetWidth) {
                return;
            }
            Map<Integer, Integer> maxColumnWidthMap = cache.computeIfAbsent(writeSheetHolder.getSheetNo(), key -> new HashMap<>(16));
            Integer columnWidth = dataLength(cellDataList, cell, isHead);
            if (columnWidth < 0) {
                return;
            }
            if (columnWidth > MAX_COLUMN_WIDTH) {
                columnWidth = MAX_COLUMN_WIDTH;
            }
            Integer maxColumnWidth = maxColumnWidthMap.get(cell.getColumnIndex());
            if (maxColumnWidth == null || columnWidth > maxColumnWidth) {
                maxColumnWidthMap.put(cell.getColumnIndex(), columnWidth);
                writeSheetHolder.getSheet().setColumnWidth(cell.getColumnIndex(), columnWidth * 256);
            }
        }

        private Integer dataLength(List<WriteCellData<?>> cellDataList, Cell cell, Boolean isHead) {
            if (Boolean.TRUE.equals(isHead)) {
                return (int) Math.ceil(cell.getStringCellValue().getBytes().length * 1.2);
            }
            WriteCellData<?> cellData = cellDataList.get(0);
            CellDataTypeEnum type = cellData.getType();
            if (type == null) {
                return DEFAULT_DATA_LENGTH;
            }
            switch (type) {
                case STRING:
                    return (int) Math.ceil(cellData.getStringValue().getBytes().length * 1.2);
                case BOOLEAN:
                    return (int) Math.ceil(cellData.getBooleanValue().toString().getBytes().length * 1.2);
                case NUMBER:
                    return (int) Math.ceil(cellData.getNumberValue().toString().getBytes().length * 1.2);
                // 日期类型的数据长度固定为 25，可以包含所有日期格式
                case DATE:
                    return DEFAULT_DATE_LENGTH;
                default:
                    return DEFAULT_DATA_LENGTH;
            }
        }
    }
}
